#ifndef __SOCKRT_HPP__
#define __SOCKET_HPP__
// 封装tcp通信
// 此处采用继承的方式，使用基类指针指向子类，就可以调用上层基类封装的大体逻辑，细节上使用子类实现的具体方法

#include "Logger.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

static const int gbacklog = 16;
static const int gsockfd  = -1;

enum
{
    OK,
    SOCKET_CREATE_ERR,
    SOCKET_BIND_ERR,
    SOCKET_LISTEN_ERR
};

class Socket
{
public:
    virtual ~Socket() {};
    virtual void CreateSocketOrDie() = 0;
    virtual void BindSocketOrDie(int port) = 0;
    virtual void ListenSocketOrDie(int backlog) = 0;
    // virtual std::unique_ptr<Socket> Accept(InetAddr* clildaddr) = 0;这是错误的写法，不能使用unique_ptr
    // unique_ptr表示独占所有权，轻量级，没有额外开销。
    // shared_ptr表示共享所有权，需要维护引用计数，因此有额外的开销。  
    virtual std::shared_ptr<Socket> Accept(InetAddr* clildaddr) = 0;     
    virtual ssize_t Recv(std::string* out) = 0;
    virtual ssize_t Send(const std::string& in) = 0;
    virtual int Sockfd() =0;
    virtual void Close() = 0;

    //客户端发送连接请求
    virtual bool Connect(InetAddr& server) = 0;
public:
    // 创建tcp 监听套接字
    void BuildTcpSocketServer(int _port) // 需要传递端口号
    {
        CreateSocketOrDie();
        BindSocketOrDie(_port);
        ListenSocketOrDie(gbacklog);
    }

    //客户端创建套接字
    void  BuildTcpSocketClient()
    {
        CreateSocketOrDie();
    }
};

class TcpSocket : public Socket
{
public:
    TcpSocket():_sockfd(gsockfd) {}
    TcpSocket(int sockfd):_sockfd(sockfd)
    {}
    ~TcpSocket() {}
    void CreateSocketOrDie() override
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "创建 socket 失败";
            exit(SOCKET_CREATE_ERR);
        }
        LOG(LogLevel::INFO) << "创建 socket 成功";
    }

    // bind 与listen 返回值：成功返回0，失败返回-1
    void BindSocketOrDie(int port) override
    {
        InetAddr addr(port);
        //debug
        std::cout << addr.ToString() << std::endl;

        int n = bind(_sockfd, addr.Addr(), addr.Length());
        if (n != 0)
        {
            LOG(LogLevel::FATAL) << "绑定 socket 失败";
            exit(SOCKET_BIND_ERR);
        }
        LOG(LogLevel::INFO) << "绑定 socket 成功";
    }

    void ListenSocketOrDie(int backlog) override
    { 
        int n = listen(_sockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "监听 socket 失败";
            exit(SOCKET_LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "监听 socket 成功";
    }

    //获取客户端的连接，同时需要知道用户的地址信息->输出型参数；还需要返回accpet 得到的套接字信息
    std::shared_ptr<Socket> Accept(InetAddr* clildaddr) override
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sockfd = accept(_sockfd , (struct sockaddr*)&peer, &len);
        if(sockfd < 0)
        {
            LOG(LogLevel::ERROR) << "连接 socket 失败";
            return nullptr;//客户端不会因为没有成功获取一次连接退出
        }
        LOG(LogLevel::INFO) << "连接 socket 成功";

        //peer 中存放的是用户的地址信息
        clildaddr->Init(peer);
        return std::make_shared<TcpSocket>(sockfd);
    }
    
    //获取数据->调用recv,返回获取到的数据个数，交给上层去判断
    ssize_t Recv(std::string* out) override//输出型参数
    {
        char outbuffer[1024];
        outbuffer[0]= 0;
        ssize_t n = recv(_sockfd , outbuffer , sizeof(outbuffer)-1 ,0);
        if( n > 0)
        {
            outbuffer[n] = 0;//手动添加\0
            *out += outbuffer;
        }
        return n;
    }
    //发送数据，使用send
    ssize_t Send(const std::string& in) override
    {
        int n = send(_sockfd , in.c_str() , in.size() , 0);
        return n;
    }

    //获取文件描述符以及关闭文件描述符
    int Sockfd() override
    {
        return _sockfd;
    }

    void Close() override
    {
        if(_sockfd > 0)
            close(_sockfd);
    }

    bool Connect(InetAddr& server) override
    {
        //connect 成功返回0，失败返回-1
        int n = connect(_sockfd , server.Addr() , server.Length());
        if(n==0) return true;
        else return false;
    }

private:
    int _sockfd;
};

#endif